﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using Castle.DynamicProxy;

namespace PdfLibrary
{
    public class DictionaryObjectInterceptor : IInterceptor
    {
        public PdfDictionary BaseDictionary;
        public Dictionary<string, object> PropertyCache = new Dictionary<string, object>();

        public DictionaryObjectInterceptor(PdfDictionary baseDictionary)
        {
            this.BaseDictionary = baseDictionary;
        }

        public void Intercept(IInvocation invocation)
        {
            if (invocation.Method.Name.StartsWith("get_"))
            {
                var propName = invocation.Method.Name.Substring(4);
                object propValue = null;

                if(!PropertyCache.TryGetValue(propName, out propValue))
                {
                    {
                        IPdfObject tmp;
                        if (!this.BaseDictionary.TryGetValue(propName, out tmp))
                        {
                            var objType = invocation.Method.ReflectedType;
                            var propData = objType.GetProperty(propName);
                            bool isRequired = propData.GetCustomAttributes(typeof(RequiredAttribute), true).Length != 0;

                            if (isRequired)
                                throw new KeyNotFoundException(String.Format(@"Key ""{0}"" not found in object type ""{1}""", propName, objType));
                        }
                        else
                            propValue = tmp;
                    }

                    /* Resolve indirect reference */
                    if (propValue is PdfIndirectReference)
                        propValue = ((PdfIndirectReference)propValue).ResolvedObject;

                    var resultType = invocation.Method.ReturnType;

                    /* PdfName -> string */
                    if (resultType == typeof(string) && propValue is PdfName)
                        propValue = ((PdfName)propValue).Value;
                    /* PdfNumber -> int */
                    else if (resultType == typeof(int) && propValue is PdfNumber)
                        propValue = (int)((PdfNumber)propValue).value;
                    /* PdfNumber -> double */
                    else if (resultType == typeof(double) && propValue is PdfNumber)
                        propValue = ((PdfNumber)propValue).value;
                    else
                    {
                        /* PdfArray -> PdfAutoArray, etc */
                        var constructor = resultType.GetConstructor(new[] {propValue.GetType()});
                        if (constructor != null)
                            propValue = constructor.Invoke(new[] {propValue});
                            /* Convert PdfDictionary to the requested type instance */
                        else if (resultType != typeof (PdfDictionary) && propValue is PdfDictionary)
                            propValue = CreateProxy(resultType, (PdfDictionary) propValue);
                    }

                    PropertyCache[propName] = propValue;
                }

                invocation.ReturnValue = propValue;
            }
        }

        public static object CreateProxy(Type interfaceOrTypeToProxy, PdfDictionary dictionary)
        {
            if (interfaceOrTypeToProxy.IsInterface)
                return new ProxyGenerator().CreateInterfaceProxyWithoutTarget(interfaceOrTypeToProxy, new DictionaryObjectInterceptor(dictionary));
            else
                return new ProxyGenerator().CreateClassProxy(interfaceOrTypeToProxy, new DictionaryObjectInterceptor(dictionary));
        }

        public static T CreateProxy<T>(PdfDictionary dictionary) where T : class
        {
            return (T)CreateProxy(typeof (T), dictionary);
        }
    }
}
